package net.edwardstx.brian.rpn.types;

import net.edwardstx.brian.rpn.exception.InvalidComparison;
import net.edwardstx.brian.rpn.type.annotations.RPNOperation;

@SuppressWarnings("rawtypes")
public class RPNRational extends RPNNumeric<RPNRational.Rational> {


	public static final String TYPE_NAME = "Rational";
	public static final Type<RPNRational> TYPE = new Type<RPNRational>(TYPE_NAME,RPNRational.class);
	@Override
	public Type<RPNRational> getAssociatedType() {
		return TYPE;
	}
	
	RPNRational(Rational value) {
		super(value);
	}
	
	RPNRational(Integer num, Integer den) {
		super(new Rational(num, den));
	}
	
	
	RPNRational(Long num, Long den) {
		super(new Rational(num, den));
	}
	
	@RPNOperation(value="+")
	public static RPNNumeric addition(RPNRational l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_0.value.numanator * l_1.value.denomanator +
				l_1.value.numanator *l_0.value.denomanator,
				l_1.value.denomanator * l_0.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="+")
	public static RPNNumeric addition(RPNRational l_0, RPNInteger l_1 ){
		Rational ret = new Rational(l_0.value.numanator  +
				l_1.value *l_0.value.denomanator, l_0.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="+")
	public static RPNNumeric addition(RPNInteger l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_0.value * l_1.value.denomanator +
				l_1.value.numanator, l_1.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="-")
	public static RPNNumeric subtraction(RPNRational l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator *l_0.value.denomanator -
				l_0.value.numanator * l_1.value.denomanator,
				l_1.value.denomanator * l_0.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="-")
	public static RPNNumeric subtraction(RPNRational l_0, RPNInteger l_1 ){
		Rational ret = new Rational(l_1.value *l_0.value.denomanator - 
				l_0.value.numanator	, l_0.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="-")
	public static RPNNumeric subtraction(RPNInteger l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator - 
				l_0.value * l_1.value.denomanator
				, l_1.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	
	@RPNOperation(value="*")
	public static RPNNumeric multiply(RPNRational l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator *l_0.value.numanator,
				l_0.value.denomanator * l_1.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="*")
	public static RPNNumeric multiply(RPNInteger l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator *l_0.value, l_1.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="*")
	public static RPNNumeric multiply(RPNRational l_0, RPNInteger l_1 ){
		Rational ret = new Rational(l_1.value *l_0.value.numanator,l_0.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="/")
	public static RPNNumeric divide(RPNRational l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator *l_0.value.denomanator,
				l_0.value.numanator * l_1.value.denomanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="/")
	public static RPNNumeric divide(RPNInteger l_0, RPNRational l_1 ){
		Rational ret = new Rational(l_1.value.numanator, l_1.value.denomanator *l_0.value);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}
	
	@RPNOperation(value="/")
	public static RPNNumeric divide(RPNRational l_0, RPNInteger l_1 ){
		Rational ret = new Rational(l_1.value *l_0.value.denomanator,l_0.value.numanator);
		 if(ret.isInteger())
			 return new RPNInteger(ret.numanator);
		 return new RPNRational(ret);
	}	
	
	@RPNOperation(value="INV")
	public static RPNNumeric inv(RPNRational l_0 ){
		if(l_0.value.numanator == 1)
			return new RPNInteger(l_0.value.denomanator);
		return new RPNRational(l_0.value.denomanator,l_0.value.numanator);
	}
	
	
	@RPNOperation(value="MAX")
	public static RPNNumeric max(RPNRational l_0, RPNInteger l_1 ){
		if(l_0.compareTo(l_1) < 0)
			return l_0;
		return l_1;
	}
	
	@RPNOperation(value="MAX")
	public static RPNNumeric max(RPNInteger l_0, RPNRational l_1 ){
		if(l_1.compareTo(l_0) > 0)
			return l_0;
		return l_1;
	}
	
	@RPNOperation(value="MIN")
	public static RPNNumeric min(RPNRational l_0, RPNInteger l_1 ){
		if(l_0.compareTo(l_1) > 0)
			return l_0;
		return l_1;
	}
	
	@RPNOperation(value="MIN")
	public static RPNNumeric min(RPNInteger l_0, RPNRational l_1 ){
		if(l_1.compareTo(l_0) > 0)
			return l_0;
		return l_1;
	}
	
	
	@Override
	public int compareTo(RPNNumeric arg0) {
		if(arg0 instanceof RPNRational){
			RPNRational rhs = (RPNRational) arg0;
			return this.value.compareTo(rhs.value);
		} 
		if(arg0 instanceof RPNInteger){
			RPNInteger rhs = (RPNInteger) arg0;
			Rational rhsValue = new Rational(rhs.value, 1);
			return this.value.compareTo(rhsValue);
		} 
		throw new InvalidComparison();
	}
	
	public static class Rational extends Number implements Comparable<Rational> {
		private final long numanator;
		private final long denomanator;
		
		public long GCD(long a, long b) { return b==0 ? a : GCD(b, a%b); }
		
		public Rational(long num, long den){
			long gdc = GCD(num,den);
			this.numanator = num / gdc;
			this.denomanator = den / gdc;
		}
		
		@Override
		public double doubleValue() {
			double num = numanator;
			double den = denomanator;
			return num / den;
		}

		@Override
		public float floatValue() {
			float num = numanator;
			float den = denomanator;
			return num / den;
		}

		@Override
		public int intValue() {
			int num = (int) numanator;
			int den = (int) denomanator;
			return num / den;
		}

		@Override
		public long longValue() {
			return numanator / denomanator;
		}

		public long getNumanator() {
			return numanator;
		}

		public long getDenomanator() {
			return denomanator;
		}

		@Override
		public int compareTo(Rational o) {
			long lhs = this.numanator * o.denomanator;
			long rhs = o.numanator * this.denomanator;
			Long ret = new Long(lhs - rhs);
			return ret.intValue();
		}
		
		public int compareTo(Integer o) {
			long lhs = this.numanator;
			long rhs = o * this.denomanator;
			Long ret = new Long(lhs - rhs);
			return ret.intValue();
		}
		
		public boolean isInteger(){
			return this.denomanator == 1;
		}
		
	}
	
	@Override
	public String toString(){
		return value.numanator + "/" + value.denomanator;
	}

}
